"""
人物定型服务
实现人物参考图生成、筛选和锁定流程
"""
from pathlib import Path
from typing import Optional, List, Dict, Any
from datetime import datetime
from loguru import logger

from storyforge.config import get_config, get_provider_config
from storyforge.models import Character, ReferenceImage
from storyforge.utils import (
    get_project_dir, load_json, save_json, generate_id
)
from storyforge.providers.image_factory import ImageProviderFactory
import time



class CharacterService:
    """人物定型服务"""
    
    def __init__(self):
        self.config = get_config()
    
    def generate_references(
        self,
        project_name: str,
        character_name: Optional[str] = None,
        count: Optional[int] = None,
        provider_name: Optional[str] = None,
        age: Optional[int] = None,
        episode_range: Optional[str] = None,
        time_label: Optional[str] = None,
    ) -> Dict[str, List[ReferenceImage]]:
        """
        为角色生成参考图
        
        Args:
            project_name: 项目名称
            character_name: 角色名称（可选，不指定则为所有主要角色生成）
            count: 每个角色生成的参考图数量（可选，默认使用配置）
            provider_name: 图像生成提供商（可选，默认使用配置）
            age: 本次参考图对应的角色年龄阶段（可选，例如 18 表示 18 岁左右形象）
            episode_range: 本次参考图适用的剧集范围（可选，例如 "1-20"）
            time_label: 时间/阶段标签（可选，例如 "学生时代"、"末日前"、"末日25年后"）
            
        Returns:
            字典，键为角色名称，值为参考图列表
        """
        logger.info(
            "开始生成角色参考图: project=%s, character=%s, age=%s, episodes=%s, time_label=%s",
            project_name,
            character_name,
            age,
            episode_range,
            time_label,
        )
        
        # 获取项目目录
        project_dir = get_project_dir(project_name, self.config.paths.projects)
        
        # 读取详细大纲，提取角色信息
        detailed_outline_path = project_dir / "detailed_outline.json"
        if not detailed_outline_path.exists():
            raise FileNotFoundError(f"未找到详细大纲文件: {detailed_outline_path}")
        
        outline_data = load_json(str(detailed_outline_path))
        characters_data = outline_data.get("characters", [])
        
        if not characters_data:
            raise ValueError("详细大纲中没有角色信息")
        
        # 过滤角色：如果指定了角色名称，只处理该角色；否则处理所有主要角色
        if character_name:
            characters_data = [c for c in characters_data if c.get("name") == character_name]
            if not characters_data:
                raise ValueError(f"未找到角色: {character_name}")

        logger.info(f"角色数据: {characters_data}")
        
        # 确定生成数量
        ref_count = count or self.config.generation.characterReferences.count
        ref_types = self.config.generation.characterReferences.types
        
        # 确定提供商
        if not provider_name:
            provider_name = self.config.defaultProvider.image
        
        provider_config = get_provider_config(provider_name)
        if not provider_config:
            raise ValueError(f"未找到图像提供商配置: {provider_name}")
        
        logger.info(f"使用提供商: {provider_name}，每个角色生成 {ref_count} 张参考图")
        
        result = {}
        
        # 创建图像提供商
        image_provider = ImageProviderFactory.create_provider(provider_name)

        # 为每个角色生成参考图
        for char_data in characters_data:
            char_name = char_data.get("name")
            logger.info(f"为角色 '{char_name}' 生成参考图...")
            
            # 创建角色目录
            char_dir = project_dir / "characters" / char_name
            ref_dir = char_dir / "references"
            ref_dir.mkdir(parents=True, exist_ok=True)
            
            # 构建生成提示词
            # appearance 字段是必须的，如果缺失则报错
            visual_desc = char_data.get("appearance", "")
            if not visual_desc:
                error_msg = f"角色 '{char_name}' 缺少 appearance 字段。请重新生成详细大纲，确保包含完整的外貌描述。"
                logger.error(error_msg)
                raise ValueError(error_msg)
            
            style_prompt = self._build_style_prompt(char_data)
            negative_prompt = self._build_negative_prompt()
            
            # 为每个角色生成一个固定的 base_seed（基于角色名称）
            import hashlib
            char_name_hash = hashlib.md5(char_name.encode('utf-8')).hexdigest()
            base_seed = int(char_name_hash[:8], 16) % 1000000  # 生成 0-999999 的种子
            logger.info(f"  角色 '{char_name}' 的 base_seed: {base_seed}")
            
            references = []
            
            # 生成多张参考图
            for i in range(ref_count):
                ref_type = ref_types[i % len(ref_types)]
                
                # 为每张参考图计算不同的 seed（基于 base_seed + 索引）
                ref_seed = base_seed + i
                logger.info(f"  生成参考图 {i+1}/{ref_count} ({ref_type}, seed={ref_seed})...")
                
                # 构建完整提示词（带阶段/年龄信息，用于随剧情演化的人物形象）
                full_prompt = self._build_reference_prompt(
                    visual_desc=visual_desc,
                    ref_type=ref_type,
                    style_prompt=style_prompt,
                    age=age,
                    episode_range=episode_range,
                    time_label=time_label,
                )

                # 解析分辨率
                resolution = provider_config.extra.get("resolution", "1024*1024")
                width, height = self._parse_resolution_to_wh(resolution)

                logger.info(f"full_prompt: {full_prompt}")
                logger.info(f"width: {width}")
                logger.info(f"height: {height}")
                logger.info(f"style: {provider_config.extra.get('style')}")
                logger.info(f"seed: {ref_seed}")
                logger.info(f"character_name: {char_name}")

                # 提交任务并获取结果URL
                try:
                    job_id = image_provider.submit({
                        "prompt": full_prompt,
                        "width": width,
                        "height": height,
                        "style": provider_config.extra.get("style"),
                        "seed": ref_seed,  # 新增：传递 seed
                        "character_name": char_name  # 新增：传递角色名称（用于占位图）
                    })
                    # 轮询等待（最多 ~120s，通义万相通常需要 60-90 秒）
                    result_url = None
                    max_retries = 40  # 40次 × 3秒 = 120秒
                    for attempt in range(max_retries):
                        poll = image_provider.poll(job_id)
                        status = poll.get("status")
                        
                        if status == "succeeded" and poll.get("result_url"):
                            result_url = poll.get("result_url")
                            logger.info(f"    图片生成成功（耗时约 {(attempt + 1) * 3} 秒）")
                            break
                        elif status == "failed":
                            raise RuntimeError(f"生成失败: {poll.get('error')}")
                        
                        # 每次等待 3 秒
                        if attempt < max_retries - 1:  # 最后一次不用等待
                            time.sleep(3)
                    
                    if not result_url:
                        raise RuntimeError(f"生成超时（等待了 {max_retries * 3} 秒），状态: {poll}")
                except Exception as e:
                    logger.warning(f"图像提供商调用失败，使用占位图: {e}")
                    result_url = f"https://placehold.co/{width}x{height}.png?text=ref"

                # 决定文件扩展名（尽可能与URL一致，默认png）
                ext = ".png"
                if isinstance(result_url, str) and result_url.lower().endswith(".jpg"):
                    ext = ".jpg"

                ref_id = generate_id("ref_")
                ref_filename = f"{ref_type}_{ref_id}{ext}"
                ref_path = ref_dir / ref_filename

                # 下载图片到本地
                self._download_image(result_url, ref_path)
                
                # 创建参考图对象
                ref_image = ReferenceImage(
                    id=ref_id,
                    path=str(ref_path.relative_to(project_dir)),
                    type=ref_type,
                    selected=False,
                    seed=ref_seed,  # 保存 seed 值，便于复现
                    prompt=full_prompt,
                    generatedAt=datetime.now(),
                    provider=provider_name,
                    resolution=provider_config.extra.get("resolution", "1024x1024"),
                    age=age,
                    episodeRange=episode_range,
                    timeLabel=time_label,
                )
                
                references.append(ref_image)
            
            # 加载或创建角色文件
            char_file = char_dir / f"character_{char_name}.json"
            if char_file.exists():
                char_obj = Character(**load_json(str(char_file)))
            else:
                char_obj = Character(
                    id=generate_id("char_"),
                    projectId=project_name,
                    name=char_name,
                    role=char_data.get("role", "主要角色"),
                    bio=char_data.get("bio", ""),
                    visualDescription=visual_desc,
                    stylePrompt=style_prompt,
                    negativePrompt=negative_prompt
                )
            
            # 添加新生成的参考图
            char_obj.referenceImages.extend(references)
            
            # 保存角色文件
            save_json(char_obj.dict(), str(char_file))
            logger.info(f"  已保存 {len(references)} 张参考图到 {char_file}")
            
            result[char_name] = references
        
        logger.info(f"角色参考图生成完成，共处理 {len(result)} 个角色")
        return result

    def _parse_resolution_to_wh(self, resolution: str) -> (int, int):
        """解析 '1024*1024' 或 '1080x1920' 为 (w, h)"""
        try:
            if "*" in resolution:
                w, h = resolution.split("*", 1)
            elif "x" in resolution.lower():
                w, h = resolution.lower().split("x", 1)
            else:
                w, h = "1024", "1024"
            return int(w), int(h)
        except Exception:
            return 1024, 1024

    def _download_image(self, url: str, out_path: Path):
        """下载图片到指定路径；失败则创建空文件占位"""
        import requests
        try:
            resp = requests.get(url, timeout=60)
            if resp.status_code != 200:
                raise Exception(f"下载失败: {resp.status_code}")
            out_path.write_bytes(resp.content)
        except Exception as e:
            logger.warning(f"下载图片失败，创建占位文件: {e} -> {out_path}")
            out_path.touch()
    
    def select_reference(
        self,
        project_name: str,
        character_name: str,
        image_path: str
    ) -> Character:
        """
        筛选并锁定角色的主参考图
        
        Args:
            project_name: 项目名称
            character_name: 角色名称
            image_path: 选中的参考图路径
            
        Returns:
            更新后的Character对象
        """
        logger.info(f"锁定角色参考图: {character_name} -> {image_path}")
        
        # 获取项目目录
        project_dir = get_project_dir(project_name, self.config.paths.projects)
        
        # 读取角色文件
        char_dir = project_dir / "characters" / character_name
        char_file = char_dir / f"character_{character_name}.json"
        
        if not char_file.exists():
            raise FileNotFoundError(f"未找到角色文件: {char_file}，请先运行 'character generate'")
        
        char_obj = Character(**load_json(str(char_file)))
        
        # 验证图片路径
        image_path_obj = Path(image_path)
        if not image_path_obj.is_absolute():
            # 相对路径，转换为相对于项目目录的路径
            image_path_obj = project_dir / image_path
        
        if not image_path_obj.exists():
            raise FileNotFoundError(f"参考图文件不存在: {image_path_obj}")
        
        # 转换为相对于项目目录的路径
        relative_path = str(image_path_obj.relative_to(project_dir))
        
        # 更新参考图的 selected 状态
        for ref in char_obj.referenceImages:
            if ref.path == relative_path:
                ref.selected = True
            else:
                ref.selected = False
        
        # 设置主参考图和锁定状态
        char_obj.primaryReference = relative_path
        char_obj.locked = True
        char_obj.lockedAt = datetime.now()
        
        # 保存更新后的角色文件
        save_json(char_obj.dict(), str(char_file))
        
        logger.info(f"角色 '{character_name}' 已锁定，主参考图: {relative_path}")
        return char_obj
    
    def check_character_locked(
        self,
        project_name: str,
        character_names: List[str]
    ) -> Dict[str, bool]:
        """
        检查角色是否已完成人物定型（锁定）
        
        Args:
            project_name: 项目名称
            character_names: 角色名称列表
            
        Returns:
            字典，键为角色名称，值为是否已锁定
        """
        project_dir = get_project_dir(project_name, self.config.paths.projects)
        result = {}
        
        for char_name in character_names:
            char_dir = project_dir / "characters" / char_name
            char_file = char_dir / f"character_{char_name}.json"
            
            if not char_file.exists():
                result[char_name] = False
                continue
            
            try:
                char_obj = Character(**load_json(str(char_file)))
                result[char_name] = char_obj.locked
            except Exception as e:
                logger.error(f"读取角色文件失败: {char_file}, {e}")
                result[char_name] = False
        
        return result
    
    def get_unlocked_characters(
        self,
        project_name: str,
        character_names: List[str]
    ) -> List[str]:
        """
        获取未锁定的角色列表
        
        Args:
            project_name: 项目名称
            character_names: 角色名称列表
            
        Returns:
            未锁定的角色名称列表
        """
        locked_status = self.check_character_locked(project_name, character_names)
        return [name for name, locked in locked_status.items() if not locked]
    
    def _build_style_prompt(self, char_data: Dict[str, Any]) -> str:
        """构建风格提示词"""
        # 这里可以根据项目类型、角色类型等构建统一的风格提示词
        base_style = "realistic, cinematic lighting, 4k, high quality"
        
        # 根据角色性别调整（如果 appearance 中没有包含性别信息）
        # 注意：如果已经从 _extract_appearance_from_character 中提取了性别，这里就不需要重复添加
        
        return base_style
    
    def _build_negative_prompt(self) -> str:
        """构建负面提示词"""
        return "ugly, blurry, low quality, distorted, deformed, disfigured, bad anatomy"
    
    def _build_reference_prompt(
        self,
        visual_desc: str,
        ref_type: str,
        style_prompt: str,
        age: Optional[int] = None,
        episode_range: Optional[str] = None,
        time_label: Optional[str] = None,
    ) -> str:
        """
        构建参考图生成提示词
        
        Args:
            visual_desc: 视觉描述
            ref_type: 参考图类型
            style_prompt: 风格提示词
            
        Returns:
            完整的生成提示词
        """
        # 根据参考图类型添加视角/表情描述
        type_hints = {
            "front_view": "front view, facing camera",
            "side_view": "side view, profile",
            "expression_smile": "smiling, happy expression",
            "expression_serious": "serious expression, focused",
            "action_walking": "walking, full body"
        }
        
        type_hint = type_hints.get(ref_type, "")
        
        # 新增：添加一致性关键词，确保同一角色的多张图片保持一致
        consistency_hint = "same character, consistent appearance, character reference sheet"

        # 阶段/年龄提示（可选），用于支持“随剧情发展的形象变化”
        phase_hints = []
        if age is not None:
            phase_hints.append(f"visible age around {age} years old")
        if episode_range:
            phase_hints.append(f"for story episodes {episode_range}")
        if time_label:
            phase_hints.append(time_label)

        parts = [visual_desc]
        if phase_hints:
            parts.append(", ".join(phase_hints))
        if type_hint:
            parts.append(type_hint)
        parts.append(consistency_hint)
        if style_prompt:
            parts.append(style_prompt)

        prompt = ", ".join([p for p in parts if p])
        return prompt.strip()

